using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using Koans.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Koans.Lessons
{
    [TestClass]
    public class Lesson1ObservableStreams
    {
        /*
     * How to Run: Press Ctrl+R,T (go ahead, try it now)
     * Step 1: find the 1st method that fails
     * Step 2: Fill in the blank ____ to make it pass
     * Step 3: run it again
     * Note: Do not change anything other than the blank
     */

        [TestMethod]
        public void SimpleSubscription()
        {
            Observable.Return(42).Subscribe(x => Assert.AreEqual(42, x));

        }

        [TestMethod]
        public void WhatGoesInComesOut()
        {
            Observable.Return(101).Subscribe(x => Assert.AreEqual(101, x));
        }
        //Q: Which interface Rx apply? (hint: what does "Return()" return)
        //A: IObserable<TResult>.

        [TestMethod]
        public void ThisIsTheSameAsAnEventStream()
        {
            var events = new Subject<int>();
            events.Subscribe(x => Assert.AreEqual(37, x));
            events.OnNext(37);
        }
        //Q: What is the relationship between "ThisIsTheSameAsAnEventStream()" and "SimpleSubscription()"?
        //A: Both subscribe on Obserable.

        [TestMethod]
        public void HowEventStreamsRelateToObservables()
        {
            int observableResult = 1;
            Observable.Return(73).Subscribe(i => observableResult = i);
            int eventStreamResult = 1;
            var events = new Subject<int>();
            events.Subscribe(i => eventStreamResult = i);
            events.OnNext(73);
            Assert.AreEqual(observableResult, eventStreamResult);
        }
        //Q: What does Observable.Return() map to for a Subject?
        //A: .OnNext().

        [TestMethod]
        public void EventStreamsHaveMultipleEvents()
        {
            int eventStreamResult = 0;
            var events = new Subject<int>();
            events.Subscribe(i => eventStreamResult += i);
            events.OnNext(10);
            events.OnNext(7);
            Assert.AreEqual(17, eventStreamResult);
        }
        //Q: What does Observable.Return() map to for a Subject?
        //A: .OnNext()

        [TestMethod]
        public void SimpleReturn()
        {
            var received = "";
            Observable.Return("Foo").Subscribe((string s) => received = s);
            Assert.AreEqual("Foo", received);
        }

        [TestMethod]
        public void TheLastEvent()
        {
            var received = "";
            var names = new[] { "Foo", "Bar" };
            names.ToObservable().Subscribe((s) => received = s);
            Assert.AreEqual("Bar", received);
        }

        [TestMethod]
        public void EveryThingCounts()
        {
            var received = 0;
            var numbers = new[] { 3, 4 };
            numbers.ToObservable().Subscribe((int x) => received += x);
            Assert.AreEqual(7, received);
        }

        [TestMethod]
        public void ThisIsStillAnEventStream()
        {
            var received = 0;
            var numbers = new Subject<int>();
            numbers.Subscribe((int x) => received += x);
            numbers.OnNext(10);
            numbers.OnNext(5);
            Assert.AreEqual(15, received);
        }

        [TestMethod]
        public void AllEventsWillBeRecieved()
        {
            var received = "Working ";
            var numbers = Range.Create(9, 5);
            numbers.ToObservable().Subscribe(x => received += x);
            Assert.AreEqual("Working 98765", received);
        }

        [TestMethod]
        public void DoingInTheMiddle()
        {
            var status = new List<String>();
            var gits = new List<string>();
            var daysTillTest = Range.Create(4, 1).ToObservable();
            daysTillTest.Do(d => status.Add(d + "=" + (d == 1 ? "Study Like Mad" : "Party"))).Subscribe();
            daysTillTest.Do(d => gits.Add(d + (d == 1 ? "=" : "!=") + "git")).Subscribe();

            Assert.AreEqual("[4=Party, 3=Party, 2=Party, 1=Study Like Mad]", status.AsString());
            Assert.AreEqual("[4!=git, 3!=git, 2!=git, 1=git]", gits.AsString());
        }

        [TestMethod]
        public void NothingListensUntilYouSubscribe()
        {
            var sum = 0;
            var numbers = Range.Create(1, 10).ToObservable();
            var observable = numbers.Do(n => sum += n);
            Assert.AreEqual(0, sum);
            observable.Subscribe();
            Assert.AreEqual(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10, sum);
        }

        [TestMethod]
        public void EventsBeforeYouSubscribedDoNotCount()
        {
            var sum = 0;
            var numbers = new Subject<int>();
            var observable = numbers.Do(n => sum += n);
            numbers.OnNext(1);
            numbers.OnNext(2);
            observable.Subscribe();
            numbers.OnNext(3);
            numbers.OnNext(4);
            Assert.AreEqual(7, sum);
        }

        [TestMethod]
        public void EventsAfterYouUnsubscribDoNotCount()
        {
            var sum = 0;
            var numbers = new Subject<int>();
            var observable = numbers.Do(n => sum += n);
            var subscription = observable.Subscribe();
            numbers.OnNext(1);
            numbers.OnNext(2);
            subscription.Dispose();
            numbers.OnNext(3);
            numbers.OnNext(4);
            Assert.AreEqual(3, sum);
        }
        [TestMethod]
        public void EventsWhileSubscribing()
        {
            var recieved = new List<string>();
            var words = new Subject<string>();
            var observable = words.Do(recieved.Add);
            words.OnNext("Peter");
            words.OnNext("said");
            var subscription = observable.Subscribe();
            words.OnNext("you");
            words.OnNext("look");
            words.OnNext("pretty");
            subscription.Dispose();
            words.OnNext("ugly");
            Assert.AreEqual("you look pretty", String.Join(" ", recieved));
        }

        [TestMethod]
        public void UnsubscribeAtAnyTime()
        {
            var received = "";
            var numbers = Range.Create(1, 9);
            IDisposable un = null;
            un = numbers.ToObservable(Scheduler.NewThread).Subscribe((int x) =>
            {
                received += x;
                if (x == 5)
                {
                    un.Dispose();
                }
            });
            Thread.Sleep(100);
            Assert.AreEqual("12345", received);
        }

        [TestMethod]
        public void TakeUntilFull()
        {
            var received = "";
            var subject = new Subject<int>();
            subject.TakeUntil(subject.Where(x => x > 5)).Subscribe(x => received += x);
            subject.OnNext(Range.Create(1, 9).ToArray());
            Assert.AreEqual("12345", received);
        }
        #region Ignore

        public object ___ = "Please Fill in the blank";
        public int ____ = 100;

        #endregion
    }
}